From d2b168aca3e304c8327dea55dc93fb9d982854dc Mon Sep 17 00:00:00 2001 From: Dmitry Shalnoff Date: Thu, 23 Jun 2016 10:23:16 +0200 Subject: [PATCH 1/1] Initial commit --- .gitignore | 56 ++++ README.md | 104 ++++++ RGBW_Controller/RGBW_Controller.ino | 482 ++++++++++++++++++++++++++++ RGBW_Controller/pwm.c | 449 ++++++++++++++++++++++++++ make | 58 ++++ 5 files changed, 1149 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 RGBW_Controller/RGBW_Controller.ino create mode 100644 RGBW_Controller/pwm.c create mode 100755 make diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..33bf192 --- /dev/null +++ b/.gitignore @@ -0,0 +1,56 @@ +memo +info + +# Prerequisites +*.d + +# Object files +*.o +*.ko +*.obj +*.elf + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.bin +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf diff --git a/README.md b/README.md new file mode 100644 index 0000000..623129c --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# RGB(W) LED Interplay Medium dendrite module (for ESP8266) + +This is IM denrite module (remote wifi network device) created for [Interplay Medium™](https://interplaymedium.org) project. + +![Interplay Medium RGB(W) LED Dendrite](https://repository.interplaymedium.org/RGBW%20Controller/IM_RGBW_LED_dendrite.jpeg) + +## Pinouts + +For LEDs be sure to add MOSFETs and current limiting resistors appropriately. Wiring scheme will be added probably later. + +![ESP8266 Pinout](https://repository.interplaymedium.org/RGBW%20Controller/esppinout_.png) +![ESP8266 Programming](https://repository.interplaymedium.org/RGBW%20Controller/usbprogram_.png) + +## Preparing the building environment + +Make sure that you have the environment installed as described at + +1. [makeEspArduino.mk](https://github.com/plerup/makeEspArduino.git) +2. [esp8266 Arduino SDK](https://github.com/esp8266/Arduino) + +3. In the *make* script, change path for each variable approprately: + MAKE_FILE=.... + ESP_SDK_ROOT=.... + +## Change your IM AXOD microserver or AP (router) WIFI login and password + +create the file + vim ../info + +assign SSID and PASSWORD of your local IM AXOD microserver or Access Point in there + + WIFI_SSID="ssid" + WIFI_PASS="ssid password" + +You can change it later whenewer you want using HTTP interface + +## Building + +initial buildong and flashing firmware at once + + ./make RGBW_Controller upload + +after that you may just build the binary and uload it using remote HTTP interface + + ./make RGBW_Controller + curl -F image=@RGBW_Controller.bin -s http://im_<....>.lan/update + +## Usage + + +By default dendrite can be reached "im_rgb5" doman + +Change it with + + curl "http://im_rgb5?rename=NEWNAME" + +Turn on the color and effect + + curl "im_rgb5?rgbw=aadd00&rotate=100" + +Other options + + curl "http://im_rgb5/help" + +## Todo + +This firmware is in progress. Here is a brief list of upcoming changes. + +* make default URL as IM_(LAST 4 DIGITS OF MAC ADDRESS) +* add interface features (save, reset....) +* state return in 2 variants + txt (default) + JSON +* add commands + switch (like rgbw=00ff0000) for latch switchers + dimm (for dimmer, HW prototype required, 220v) + rgbwdef -- save default values in EEPROM, which is returning on reset command + rotatedef -- same for rotation + +* switching AP/slave, AP by defuault + remote access setup (host name, AP/slave, SSID, passw) + save in EEPROM + +## License + +Copyright © 2016 Dmitry Shalnov [interplaymedium.org] + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this files except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + + + + + + diff --git a/RGBW_Controller/RGBW_Controller.ino b/RGBW_Controller/RGBW_Controller.ino new file mode 100644 index 0000000..b1bb372 --- /dev/null +++ b/RGBW_Controller/RGBW_Controller.ino @@ -0,0 +1,482 @@ +/* +/* + * RGB(W) LED IM Dendrite module (for ESP8266) + * Created for Interplaymedium™ project (https://interplaymedium.org) + * Copyright © 2016 Dmitry Shalnov [interplaymedium.org] + * Licensed under the Apache License, Version 2.0 +*/ + +#include "../../info" + +#include +#include +#include +#include +#include + +#define VERSION "0.1.4" + +#define EEPROM_STR_MAX_LEN 16 +#define EEPROM_OTHER_MAX_LEN 8 + +#define EEPROM_STR 0 +#define EEPROM_STR_FLAG 17 // watch overlap: EEPROM_STR_MAX_LEN +#define EEPROM_R 18 +#define EEPROM_G 19 +#define EEPROM_B 20 +#define EEPROM_W 21 +#define EEPROM_ROT_H 22 +#define EEPROM_ROT_L 23 + +// -------------------- PWM settings ---------------------------------------------------- + +extern "C"{ + #include "pwm.h" // Includes of Expressif SDK +} + +#define LED1 0 +#define LED2 2 +#define LED3 3 +#define LED4 1 + +#define PWM_CHANNELS 4 + +#define PWMSTEPS 255 +#define PWM_PERIOD 5000 // Period of PWM frequency, SDK default: 5000 -> * 200ns ^= 1 kHz + +unsigned int CIEL8[ PWMSTEPS ]; + +int R=0, G=0, B=0, W=0; +unsigned int Rnew=0, Gnew=0, Bnew=0, Wnew=0; +unsigned int rotateDelay = 0; +int signR = 1, signG = 1, signB = 1, signW = 1; +unsigned int eTimer = 0; + +uint32 pwm_duty_init[PWM_CHANNELS]; // PWM initial duty: OFF by default + +uint32 io_info[PWM_CHANNELS][3] = { + +// MUX, FUNC, PIN + +// {PERIPHS_IO_MUX_GPIO5_U, FUNC_GPIO5, 5}, // D1 +// {PERIPHS_IO_MUX_GPIO4_U, FUNC_GPIO4, 4}, // D2 + {PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0, LED1}, // D3 0 + {PERIPHS_IO_MUX_GPIO2_U, FUNC_GPIO2, LED2}, // D4 2 + + {PERIPHS_IO_MUX_U0RXD_U, FUNC_GPIO3, LED3}, // RX 3 + {PERIPHS_IO_MUX_U0TXD_U, FUNC_GPIO1, LED4}, // TX 1 + +// {PERIPHS_IO_MUX_MTMS_U, FUNC_GPIO14, 14}, // D5 +// {PERIPHS_IO_MUX_MTDI_U, FUNC_GPIO12, 12}, // D6 +// {PERIPHS_IO_MUX_MTCK_U, FUNC_GPIO13, 13}, // D7 +// {PERIPHS_IO_MUX_MTDO_U, FUNC_GPIO15 ,15}, // D8 + +// D0 - not have PWM :-( 16 +}; + +// ------------------- server settings -------------------------------------------------- + +#define HOST_DEFAULT "im_rgb5" + +char host[ EEPROM_STR_MAX_LEN ]; + +const char* ssid = IM_WIFI_SSID; +const char* password = IM_WIFI_PASS; + +ESP8266WebServer server(80); + +String HTTPresp; + +// ----------------- change current R G B (W) to another RGBW set ---------------------- + +void changeRGBto( int newR, int newG, int newB, int newW ){ + + float stepR, stepG, stepB, stepW; + int R2, G2, B2, W2; +/* +// int R, G, B, W; + +// R = EEPROM.read( 0 ); +// G = EEPROM.read( 1 ); +// B = EEPROM.read( 2 ); +// W = EEPROM.read( 3 ); +*/ + stepR = (newR - R) / (float)PWMSTEPS; + stepG = (newG - G) / (float)PWMSTEPS; + stepB = (newB - B) / (float)PWMSTEPS; + stepW = (newW - W) / (float)PWMSTEPS; + + for ( unsigned int a = 0; a < PWMSTEPS; a++ ){ + + R2 = R + round( stepR * (float)a ); + G2 = G + round( stepG * (float)a ); + B2 = B + round( stepB * (float)a ); + W2 = W + round( stepW * (float)a ); + + pwm_set_duty( CIEL8[R2], 0 ); + pwm_set_duty( CIEL8[G2], 1 ); + pwm_set_duty( CIEL8[B2], 2 ); + pwm_set_duty( CIEL8[W2], 3 ); + + pwm_start(); // commit + + delay( 500 / PWMSTEPS ); + } +} + +// ----------- explode for selected substring ----------- + +String expld( String str, unsigned int numb, char delimiter ){ + + unsigned int cnt = 0, a = 0, p2 = 0, p1 = 0, lng = 0; + + lng = str.length(); + + for ( a = 0; a < lng; a++ ){ + if ( str.charAt( a ) == delimiter ) { + p2 = p1; + p1 = a; + if ( cnt == numb ) break; + cnt ++; + } + } + + if ( a == lng ) { + p2 = p1; + p1 = lng; + } + + if ( numb > 0 ) p2 ++; + + return str.substring(p2, p1); + +} + +unsigned char URIHasArg( String str, String arg ){ + + + unsigned char a = 0, delimiterCnt = 0, lng = str.length(); + + for ( a = 0; a < lng; a++ ){ + if ( str.charAt( a ) == '/' ) delimiterCnt++; + } + + for ( a=0; a < 10; a++ ){ + if ( arg.equals( expld( str, a, '/' ) ) ) return a; + } + return 0; +} + +// ---------------- EEPROM String r/w ------------------- + +void EEPROMStrRead( unsigned char addr, char * str ){ + for (unsigned char a = addr; a < EEPROM_STR_MAX_LEN; a++ ){ + str[ a ] = EEPROM.read( a ); + if (str[ a ] == 0) break; + } +} + +void EEPROMStrWrite( unsigned char addr, char * str ){ + unsigned char a = 0; + for (a = addr; a < EEPROM_STR_MAX_LEN; a++ ){ + EEPROM.write( a, str[ a ] ); + if (str[ a ] == 0) break; + } + EEPROM.write( a, 0 ); +} + +// ---------------- misc --------------------------------- + +int str2HEX(const String str) { + return strtol( str.c_str(), 0, 16 ); +} + +void blink( unsigned char times ){ + for (unsigned char a = 0; a < times; a++ ){ + digitalWrite(LED4, HIGH); + delay (50); + digitalWrite(LED4, LOW); + delay (50); + } +} + +// --------------- Init -------------------------------- + +void setup(void) { + + HTTPresp.reserve(800); // lenght of Help message generally + + // calculate lookup array + + for (unsigned int a = 1; a < PWMSTEPS; a++) CIEL8[ a ] = round( pow( PWM_PERIOD, (double)a / (PWMSTEPS-1) ) ); // calculate exponential lookup array for PWM + CIEL8[ 0 ] = 0; + + // init LED pins + + pinMode(LED1, OUTPUT); + pinMode(LED2, OUTPUT); + pinMode(LED3, OUTPUT); + pinMode(LED4, OUTPUT); + + digitalWrite(LED1, LOW); + digitalWrite(LED2, LOW); + digitalWrite(LED3, LOW); + digitalWrite(LED4, LOW); + + // PWM inti + + for (uint8_t channel = 0; channel < PWM_CHANNELS; channel++) pwm_duty_init[channel] = 0; // Initial duty -> all off + uint32_t period = PWM_PERIOD; + pwm_init(period, pwm_duty_init, PWM_CHANNELS, io_info); + pwm_start(); + + // Serial init +#if SERIAL == 1 + Serial.begin(115200); + Serial.println(); + Serial.println("Booting Sketch...0"); +#endif + + // mDNS init + +// deviceURI.reserve(7); +// deviceURI = "im" + WiFi.macAddress().substring(12, 14) + WiFi.macAddress().substring(15, 17); // 00:00:00:00:00:00 + + EEPROM.begin( EEPROM_STR_MAX_LEN + EEPROM_OTHER_MAX_LEN ); + + if ( EEPROM.read( EEPROM_STR_FLAG ) == 1 ){ + EEPROMStrRead( EEPROM_STR, host ); + } else { + strcpy( host, HOST_DEFAULT ); // default URI and host name + } + + WiFi.hostname( host ); + +// WiFi.softAP(APssid, APpassword); + +// WiFi.mode(WIFI_AP); +// WiFi.mode(WIFI_AP_STA); + WiFi.mode(WIFI_STA); + WiFi.begin(ssid, password); + + if (WiFi.waitForConnectResult() == WL_CONNECTED) { + +// MDNS.begin( deviceURI.c_str() ); + MDNS.begin( host ); + + // default + + server.onNotFound( []() { + server.sendHeader("Connection", "close"); + + unsigned int RGBW = 0xff; + String param = ""; + String command = ""; + + // parse plain URI arguments TODO: not sure whether we need it, check Plan 9 specifications +/* + param = server.arg("rgbw"); + if ( param == "" ) { + param = URIHasArg( server.uri(), "rgbw" ); + } +*/ + // color change + + if ( server.hasArg("rgbw") ){ // || param != 0 + + param = server.arg("rgbw"); + + Rnew = str2HEX( param.substring(0, 2) ); + Gnew = str2HEX( param.substring(2, 4) ); + Bnew = str2HEX( param.substring(4, 6) ); + Wnew = str2HEX( param.substring(6, 8) ); + + changeRGBto( Rnew, Gnew, Bnew, Wnew ); + + R = Rnew; + G = Gnew; + B = Bnew; + W = Wnew; + } + + // rotate + + if ( server.hasArg("rotate") ){ + rotateDelay = str2HEX( server.arg("rotate") ); + } + + // rename host + + if ( server.hasArg("rename") ) { + EEPROMStrWrite( EEPROM_STR, (char *)server.arg("rename").c_str() ); + EEPROM.write( EEPROM_STR_FLAG, 1 ); + EEPROM.commit(); + + server.sendHeader("Connection", "close"); + server.send_P(200, "text/plain", PSTR("Done. System rebooting...\n") ); + + delay(500); + ESP.restart(); + } + + // ------ + + HTTPresp = "host: " + String(host) + ".lan" + "\n"; + HTTPresp += "URI: " + server.uri() + "\n"; +// HTTPresp += "Arg RGBW: " + String( URIHasArg( server.uri(), "test2" ) ) + "\n"; // URI contains /test2/ +// HTTPresp += "Arg RGBW: " + server.arg("aaaa") + "\n"; // GET or POST params has "aaaa" + HTTPresp += "rotate: " + String(rotateDelay) + "\n"; + HTTPresp += "rgbw: " + String(R) + " " + String(G) + " " + String(B) + " " + String(W) + "\n"; + + server.send(200, "text/plain", HTTPresp); + }); + + // test EEPROM string + + server.on("/eeprom", HTTP_GET, []() { + + char * testStr2 = "asdfghjkl12345"; + + server.setContentLength(CONTENT_LENGTH_UNKNOWN); + server.send(200, "text/plain", ""); + + EEPROMStrRead( EEPROM_STR, testStr2 ); + + server.sendContent( testStr2 ); + server.sendContent( "\n" ); + }); + + // help + + server.on("/help", HTTP_GET, []() { + + HTTPresp = "Interplay Medium ESP8266 LED RGB(W) PWM Controller. Version: " + String(VERSION) + "\n"; + HTTPresp += "Written by Dmitry Shalnov (c) 2017. License GPLv3+: GNU GPL version 3 or later . \n\n"; + + HTTPresp += " rgbw Red Green Blue and White components in hexadecimal form (e.g. ff00ff00)\n"; + HTTPresp += " rotate Delay of components rotation FX in hexadecimal (e.g. ffff), set 0 to stop\n"; + HTTPresp += " update Wireless update of firmware (see example below)\n"; + HTTPresp += " help Send this help\n\n"; + + HTTPresp += "Usage: curl http://" + String(host) + ".lan [--data \"rgbw=\"] [--data \"rotate=\"] \n"; + HTTPresp += " curl http://" + String(host) + ".lan[/rgbw/][/rotate/] \n"; + HTTPresp += "Examples: curl http://" + String(host) + ".lan/?rgbw=00ff00ff&rotate=ff \n"; + HTTPresp += " curl -F image=@RGBW_Controller.bin " + String(host) + ".lan/update \n"; + + server.sendHeader("Connection", "close"); + server.send( 200, "text/plain", HTTPresp ); + + }); + + // OTA update + + server.on("/update", HTTP_POST, []() { + + server.sendHeader("Connection", "close"); + server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); + + ESP.restart(); + + }, []() { + + HTTPUpload& upload = server.upload(); + + if (upload.status == UPLOAD_FILE_START) { + + rotateDelay = 0; + blink( 3 ); + +#if SERIAL == 1 + Serial.setDebugOutput(true); +#endif + WiFiUDP::stopAll(); + +#if SERIAL == 1 + Serial.printf("Update: %s\n", upload.filename.c_str()); +#endif + uint32_t maxSketchSpace = (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000; + + if (!Update.begin(maxSketchSpace)) { //start with max available size +#if SERIAL == 1 + Update.printError(Serial); +#endif + } + } else if (upload.status == UPLOAD_FILE_WRITE) { + if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { +#if SERIAL == 1 + Update.printError(Serial); +#endif + } + } else if (upload.status == UPLOAD_FILE_END) { + if (Update.end(true)) { //true to set the size to the current progress + + server.sendHeader("Connection", "close"); + server.send_P(200, "text/plain", PSTR("Success. Please wait until device replace firmware and boot up...\n") ); +#if SERIAL == 1 + Serial.printf("Update Success: %u\nRebooting....\n", upload.totalSize); +#endif + } else { + server.sendHeader("Connection", "close"); + server.send_P(200, "text/plain", PSTR("Something went wrong. Please reset device and try again.\n") ); +#if SERIAL == 1 + Update.printError(Serial); +#endif + } +#if SERIAL == 1 + Serial.setDebugOutput(false); +#endif + } + + yield(); + }); + + // start server + + server.begin(); + MDNS.addService("http", "tcp", 80); +#if SERIAL == 1 + Serial.printf("Ready! Open http://%s.local in your browser\n", host); +#endif + + } else { +#if SERIAL == 1 + Serial.println("WiFi Failed"); +#endif + } +} + +void loop(void) { + + server.handleClient(); + MDNS.update(); + + eTimer ++; + + if ( eTimer >= rotateDelay && rotateDelay != 0 ) { + + eTimer = 0; + + R = R + signR; + G = G + signG; + B = B + signB; + W = W + signW; + + if ( R >= PWMSTEPS-1 ) { signR = -1; R = PWMSTEPS-1; } + if ( G >= PWMSTEPS-1 ) { signG = -1; G = PWMSTEPS-1; } + if ( B >= PWMSTEPS-1 ) { signB = -1; B = PWMSTEPS-1; } + if ( W >= PWMSTEPS-1 ) { signW = -1; W = PWMSTEPS-1; } + + if ( R < 2 ) { signR = 1; R = 2; } + if ( G < 2 ) { signG = 1; G = 2; } + if ( B < 2 ) { signB = 1; B = 2; } + if ( W < 2 ) { signW = 1; W = 2; } + + pwm_set_duty( CIEL8[R], 0 ); + pwm_set_duty( CIEL8[G], 1 ); + pwm_set_duty( CIEL8[B], 2 ); + pwm_set_duty( CIEL8[W], 3 ); + + pwm_start(); // commit + + } +} diff --git a/RGBW_Controller/pwm.c b/RGBW_Controller/pwm.c new file mode 100644 index 0000000..6df21ac --- /dev/null +++ b/RGBW_Controller/pwm.c @@ -0,0 +1,449 @@ +/* + * Copyright (C) 2016 Stefan Brüns + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Set the following three defines to your needs */ + +#ifndef SDK_PWM_PERIOD_COMPAT_MODE + #define SDK_PWM_PERIOD_COMPAT_MODE 0 +#endif +#ifndef PWM_MAX_CHANNELS + #define PWM_MAX_CHANNELS 8 +#endif +#define PWM_DEBUG 0 +#define PWM_USE_NMI 0 + +/* no user servicable parts beyond this point */ + +#define PWM_MAX_TICKS 0x7fffff +#if SDK_PWM_PERIOD_COMPAT_MODE +#define PWM_PERIOD_TO_TICKS(x) (x * 0.2) +#define PWM_DUTY_TO_TICKS(x) (x * 5) +#define PWM_MAX_DUTY (PWM_MAX_TICKS * 0.2) +#define PWM_MAX_PERIOD (PWM_MAX_TICKS * 5) +#else +#define PWM_PERIOD_TO_TICKS(x) (x) +#define PWM_DUTY_TO_TICKS(x) (x) +#define PWM_MAX_DUTY PWM_MAX_TICKS +#define PWM_MAX_PERIOD PWM_MAX_TICKS +#endif + +#include +#include +#include +#include + +// from SDK hw_timer.c +#define TIMER1_DIVIDE_BY_16 0x0004 +#define TIMER1_ENABLE_TIMER 0x0080 + +struct pwm_phase { + uint32_t ticks; ///< delay until next phase, in 200ns units + uint16_t on_mask; ///< GPIO mask to switch on + uint16_t off_mask; ///< GPIO mask to switch off +}; + +/* Three sets of PWM phases, the active one, the one used + * starting with the next cycle, and the one updated + * by pwm_start. After the update pwm_next_set + * is set to the last updated set. pwm_current_set is set to + * pwm_next_set from the interrupt routine during the first + * pwm phase + */ +typedef struct pwm_phase (pwm_phase_array)[PWM_MAX_CHANNELS + 2]; +static pwm_phase_array pwm_phases[3]; +static struct { + struct pwm_phase* next_set; + struct pwm_phase* current_set; + uint8_t current_phase; +} pwm_state; + +static uint32_t pwm_period; +static uint32_t pwm_period_ticks; +static uint32_t pwm_duty[PWM_MAX_CHANNELS]; +static uint16_t gpio_mask[PWM_MAX_CHANNELS]; +static uint8_t pwm_channels; + +// 3-tuples of MUX_REGISTER, MUX_VALUE and GPIO number +typedef uint32_t (pin_info_type)[3]; + +struct gpio_regs { + uint32_t out; /* 0x60000300 */ + uint32_t out_w1ts; /* 0x60000304 */ + uint32_t out_w1tc; /* 0x60000308 */ + uint32_t enable; /* 0x6000030C */ + uint32_t enable_w1ts; /* 0x60000310 */ + uint32_t enable_w1tc; /* 0x60000314 */ + uint32_t in; /* 0x60000318 */ + uint32_t status; /* 0x6000031C */ + uint32_t status_w1ts; /* 0x60000320 */ + uint32_t status_w1tc; /* 0x60000324 */ +}; +static struct gpio_regs* gpio = (struct gpio_regs*)(0x60000300); + +struct timer_regs { + uint32_t frc1_load; /* 0x60000600 */ + uint32_t frc1_count; /* 0x60000604 */ + uint32_t frc1_ctrl; /* 0x60000608 */ + uint32_t frc1_int; /* 0x6000060C */ + uint8_t pad[16]; + uint32_t frc2_load; /* 0x60000620 */ + uint32_t frc2_count; /* 0x60000624 */ + uint32_t frc2_ctrl; /* 0x60000628 */ + uint32_t frc2_int; /* 0x6000062C */ + uint32_t frc2_alarm; /* 0x60000630 */ +}; +static struct timer_regs* timer = (struct timer_regs*)(0x60000600); + +static void ICACHE_RAM_ATTR +pwm_intr_handler(void) +{ + if ((pwm_state.current_set[pwm_state.current_phase].off_mask == 0) && + (pwm_state.current_set[pwm_state.current_phase].on_mask == 0)) { + pwm_state.current_set = pwm_state.next_set; + pwm_state.current_phase = 0; + } + + do { + // force write to GPIO registers on each loop + asm volatile ("" : : : "memory"); + + gpio->out_w1ts = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].on_mask); + gpio->out_w1tc = (uint32_t)(pwm_state.current_set[pwm_state.current_phase].off_mask); + + uint32_t ticks = pwm_state.current_set[pwm_state.current_phase].ticks; + + pwm_state.current_phase++; + + if (ticks) { + if (ticks >= 16) { + // constant interrupt overhead + ticks -= 9; + timer->frc1_int &= ~FRC1_INT_CLR_MASK; + WRITE_PERI_REG(&timer->frc1_load, ticks); + return; + } + + ticks *= 4; + do { + ticks -= 1; + // stop compiler from optimizing delay loop to noop + asm volatile ("" : : : "memory"); + } while (ticks > 0); + } + + } while (1); +} + +/** + * period: initial period (base unit 1us OR 200ns) + * duty: array of initial duty values, may be NULL, may be freed after pwm_init + * pwm_channel_num: number of channels to use + * pin_info_list: array of pin_info + */ +void ICACHE_FLASH_ATTR +pwm_init(uint32_t period, uint32_t *duty, uint32_t pwm_channel_num, + uint32_t (*pin_info_list)[3]) +{ + int i, j, n; + + pwm_channels = pwm_channel_num; + if (pwm_channels > PWM_MAX_CHANNELS) + pwm_channels = PWM_MAX_CHANNELS; + + for (i = 0; i < 3; i++) { + for (j = 0; j < (PWM_MAX_CHANNELS + 2); j++) { + pwm_phases[i][j].ticks = 0; + pwm_phases[i][j].on_mask = 0; + pwm_phases[i][j].off_mask = 0; + } + } + pwm_state.current_set = pwm_state.next_set = 0; + pwm_state.current_phase = 0; + + uint32_t all = 0; + // PIN info: MUX-Register, Mux-Setting, PIN-Nr + for (n = 0; n < pwm_channels; n++) { + pin_info_type* pin_info = &pin_info_list[n]; + PIN_FUNC_SELECT((*pin_info)[0], (*pin_info)[1]); + gpio_mask[n] = 1 << (*pin_info)[2]; + all |= 1 << (*pin_info)[2]; + if (duty) + pwm_set_duty(duty[n], n); + } + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, all); + GPIO_REG_WRITE(GPIO_ENABLE_W1TS_ADDRESS, all); + + pwm_set_period(period); + +#if PWM_USE_NMI + ETS_FRC_TIMER1_NMI_INTR_ATTACH(pwm_intr_handler); +#else + ETS_FRC_TIMER1_INTR_ATTACH(pwm_intr_handler, NULL); +#endif + TM1_EDGE_INT_ENABLE(); + + timer->frc1_int &= ~FRC1_INT_CLR_MASK; + timer->frc1_ctrl = 0; + + pwm_start(); +} + +__attribute__ ((noinline)) +static uint8_t ICACHE_FLASH_ATTR +_pwm_phases_prep(struct pwm_phase* pwm) +{ + uint8_t n, phases; + + uint16_t off_mask = 0; + for (n = 0; n < pwm_channels + 2; n++) { + pwm[n].ticks = 0; + pwm[n].on_mask = 0; + pwm[n].off_mask = 0; + } + phases = 1; + for (n = 0; n < pwm_channels; n++) { + uint32_t ticks = PWM_DUTY_TO_TICKS(pwm_duty[n]); + if (ticks == 0) { + pwm[0].off_mask |= gpio_mask[n]; + } else if (ticks >= pwm_period_ticks) { + pwm[0].on_mask |= gpio_mask[n]; + } else { + if (ticks < (pwm_period_ticks/2)) { + pwm[phases].ticks = ticks; + pwm[0].on_mask |= gpio_mask[n]; + pwm[phases].off_mask = gpio_mask[n]; + } else { + pwm[phases].ticks = pwm_period_ticks - ticks; + pwm[phases].on_mask = gpio_mask[n]; + pwm[0].off_mask |= gpio_mask[n]; + } + phases++; + } + } + pwm[phases].ticks = pwm_period_ticks; + + // bubble sort, lowest to hightest duty + n = 2; + while (n < phases) { + if (pwm[n].ticks < pwm[n - 1].ticks) { + struct pwm_phase t = pwm[n]; + pwm[n] = pwm[n - 1]; + pwm[n - 1] = t; + if (n > 2) + n--; + } else { + n++; + } + } + +#if PWM_DEBUG + int t = 0; + for (t = 0; t <= phases; t++) { + ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } +#endif + + // shift left to align right edge; + uint8_t l = 0, r = 1; + while (r <= phases) { + uint32_t diff = pwm[r].ticks - pwm[l].ticks; + if (diff && (diff <= 16)) { + uint16_t mask = pwm[r].on_mask | pwm[r].off_mask; + pwm[l].off_mask ^= pwm[r].off_mask; + pwm[l].on_mask ^= pwm[r].on_mask; + pwm[0].off_mask ^= pwm[r].on_mask; + pwm[0].on_mask ^= pwm[r].off_mask; + pwm[r].ticks = pwm_period_ticks - diff; + pwm[r].on_mask ^= mask; + pwm[r].off_mask ^= mask; + } else { + l = r; + } + r++; + } + +#if PWM_DEBUG + for (t = 0; t <= phases; t++) { + ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } +#endif + + // sort again + n = 2; + while (n <= phases) { + if (pwm[n].ticks < pwm[n - 1].ticks) { + struct pwm_phase t = pwm[n]; + pwm[n] = pwm[n - 1]; + pwm[n - 1] = t; + if (n > 2) + n--; + } else { + n++; + } + } + + // merge same duty + l = 0, r = 1; + while (r <= phases) { + if (pwm[r].ticks == pwm[l].ticks) { + pwm[l].off_mask |= pwm[r].off_mask; + pwm[l].on_mask |= pwm[r].on_mask; + pwm[r].on_mask = 0; + pwm[r].off_mask = 0; + } else { + l++; + if (l != r) { + struct pwm_phase t = pwm[l]; + pwm[l] = pwm[r]; + pwm[r] = t; + } + } + r++; + } + phases = l; + +#if PWM_DEBUG + for (t = 0; t <= phases; t++) { + ets_printf("%d @%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } +#endif + + // transform absolute end time to phase durations + for (n = 0; n < phases; n++) { + pwm[n].ticks = + pwm[n + 1].ticks - pwm[n].ticks; + // subtract common overhead + pwm[n].ticks--; + } + pwm[phases].ticks = 0; + + // do a cyclic shift if last phase is short + if (pwm[phases - 1].ticks < 16) { + for (n = 0; n < phases - 1; n++) { + struct pwm_phase t = pwm[n]; + pwm[n] = pwm[n + 1]; + pwm[n + 1] = t; + } + } + +#if PWM_DEBUG + for (t = 0; t <= phases; t++) { + ets_printf("%d +%d: %04x %04x\n", t, pwm[t].ticks, pwm[t].on_mask, pwm[t].off_mask); + } + ets_printf("\n"); +#endif + + return phases; +} + +void ICACHE_FLASH_ATTR +pwm_start(void) +{ + pwm_phase_array* pwm = &pwm_phases[0]; + + if ((*pwm == pwm_state.next_set) || + (*pwm == pwm_state.current_set)) + pwm++; + if ((*pwm == pwm_state.next_set) || + (*pwm == pwm_state.current_set)) + pwm++; + + uint8_t phases = _pwm_phases_prep(*pwm); + + // all with 0% / 100% duty - stop timer + if (phases == 1) { + if (pwm_state.next_set) { +#if PWM_DEBUG + ets_printf("PWM stop\n"); +#endif + timer->frc1_ctrl = 0; + ETS_FRC1_INTR_DISABLE(); + } + pwm_state.next_set = NULL; + + GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, (*pwm)[0].on_mask); + GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, (*pwm)[0].off_mask); + + return; + } + + // start if not running + if (!pwm_state.next_set) { +#if PWM_DEBUG + ets_printf("PWM start\n"); +#endif + pwm_state.current_set = pwm_state.next_set = *pwm; + pwm_state.current_phase = phases - 1; + ETS_FRC1_INTR_ENABLE(); + RTC_REG_WRITE(FRC1_LOAD_ADDRESS, 0); + timer->frc1_ctrl = TIMER1_DIVIDE_BY_16 | TIMER1_ENABLE_TIMER; + return; + } + + pwm_state.next_set = *pwm; +} + +void ICACHE_FLASH_ATTR +pwm_set_duty(uint32_t duty, uint8_t channel) +{ + if (channel > PWM_MAX_CHANNELS) + return; + + if (duty > PWM_MAX_DUTY) + duty = PWM_MAX_DUTY; + + pwm_duty[channel] = duty; +} + +uint32_t ICACHE_FLASH_ATTR +pwm_get_duty(uint8_t channel) +{ + if (channel > PWM_MAX_CHANNELS) + return 0; + return pwm_duty[channel]; +} + +void ICACHE_FLASH_ATTR +pwm_set_period(uint32_t period) +{ + pwm_period = period; + + if (pwm_period > PWM_MAX_PERIOD) + pwm_period = PWM_MAX_PERIOD; + + pwm_period_ticks = PWM_PERIOD_TO_TICKS(period); +} + +uint32_t ICACHE_FLASH_ATTR +pwm_get_period(void) +{ + return pwm_period; +} + +uint32_t ICACHE_FLASH_ATTR +get_pwm_version(void) +{ + return 1; +} + +void ICACHE_FLASH_ATTR +set_pwm_debug_en(uint8_t print_en) +{ + (void) print_en; +} + diff --git a/make b/make new file mode 100755 index 0000000..7f1c38c --- /dev/null +++ b/make @@ -0,0 +1,58 @@ +#!/bin/bash + +if [ $# -lt 1 ]; then + echo "Usage: $0 [upload]" + exit 0 +fi + +# assign WIFI_SSID and WIFI_PASS in external file ../info +# source "../info" + +SKETCH="$1/$1.ino" + +TMP="/tmp/ESPcompile.tmp" + +MAKE_FILE="~/Bin/SDK/ESP/makeEspArduino/makeEspArduino.mk" +ESP_SDK_ROOT=~/Bin/SDK/arduino-1.8.5/hardware/esp8266com/esp8266 # keep it without quotation marks + +# nodemcuv2, generic, esp8285 +# F_CPU=80000000L +# BUILD_EXTRA_FLAGS="-DIM_WIFI_SSID=\"$WIFI_SSID\" -DIM_WIFI_PASS=\"$WIFI_PASS\"" +# make clean -f "$MAKE_FILE" ESP_ROOT=$ESP_SDK_ROOT F_CPU=80000000L CHIP=esp8266 BOARD=esp8285 SKETCH="$SKETCH" $2 +stdbuf -oL make -f "$MAKE_FILE" ESP_ROOT=$ESP_SDK_ROOT F_CPU=80000000L CHIP=esp8266 BOARD=esp8285 SKETCH="$SKETCH" $2 2>&1 | tee "$TMP" + +if [ -s "$TMP" ]; then + binSRC=$( cat "$TMP" | grep Linking | sed -e 's/Linking //g' ) + cp "$binSRC" . + + serialPort=$(cat "$TMP" | grep "opening port" | awk '{split($0,a," "); print a[3]}') +else + serialPort=/dev/ttyUSB0 +fi + +echo $serialPort + +exit 0 + + + + +sleep 1 + +if cat "$TMP" | grep -q 'error'; then + echo "exit" +else + if [ "$1" != "noserial" ]; then + + echo "Connecting $serialPort" + + while [ 1 ]; do + cat $serialPort + sleep 1 + done + else + echo "Serial terminal omited" + fi +fi + +rm "$TMP" -- 2.39.5